package thread;

import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 测试代码。
 */
public class DecimalAccountSafeTest {
    public static void main(String[] args) {
        DecimalAccount.demo(new DecimalAccountUnsafe(new BigDecimal("10000")));
        DecimalAccount.demo(new DecimalAccountSafeLock(new BigDecimal("10000")));
        DecimalAccount.demo(new DecimalAccountSafeCas(new BigDecimal("10000")));
    }
}

/**
 * 不安全的实现，有线程安全问题。
 */
class DecimalAccountUnsafe implements DecimalAccount {
    BigDecimal balance;

    public DecimalAccountUnsafe(BigDecimal balance) {
        this.balance = balance;
    }

    @Override
    public BigDecimal getBalance() {
        return balance;
    }

    @Override
    public void withdraw(BigDecimal amount) {
        BigDecimal balance = this.getBalance();
        this.balance = balance.subtract(amount);
    }

}

/**
 * 线程安全的实现，使用synchronized等悲观锁机制。
 */
class DecimalAccountSafeLock implements DecimalAccount {

    private final Object lock = new Object();
    BigDecimal balance;

    public DecimalAccountSafeLock(BigDecimal balance) {
        this.balance = balance;
    }

    @Override
    public BigDecimal getBalance() {
        return balance;
    }

    @Override
    public void withdraw(BigDecimal amount) {
        synchronized (lock) {
            BigDecimal balance = this.getBalance();
            this.balance = balance.subtract(amount);
        }
    }

}

/**
 * 使用乐观锁，CAS操作，AtomicReference类。
 */
class DecimalAccountSafeCas implements DecimalAccount {
    //获取一个可以操作【BigDecimal】的原子引用类。
    AtomicReference<BigDecimal> ref;

    public DecimalAccountSafeCas(BigDecimal balance) {
        ref = new AtomicReference<>(balance);
    }

    @Override
    public BigDecimal getBalance() {
        return ref.get();
    }

    @Override
    public void withdraw(BigDecimal amount) {
        while (true) {
            //获取之前的值。
            BigDecimal prev = ref.get();
            //获取更新之后的值。
            BigDecimal next = prev.subtract(amount);
            //进行CAS操作，如果成功了，就退出，否则，进入循环。
            if (ref.compareAndSet(prev, next)) {
                break;
            }
        }
    }

}